﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

using System.Diagnostics;

using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

using Graphene.Storing;

namespace Graphene.Processing
{
    public sealed class MultiColorImageProcessor : AbstractUniSourceProcessor, ProcessorSource
    {
        private static readonly int TOOTH_SIZE = 5;
        //private static readonly Color COLOR_1 = Color.FromArgb(201, 110, 121); //red night        good
        //private static readonly Color COLOR_1 = Color.FromArgb(1, 149, 250); //blue chewing gum paper daylight    weak    
        private static readonly Color COLOR_1 = Color.FromArgb(251, 102, 86); //orange shower gel cup daylight   best
        //private static readonly Color COLOR_1 = Color.FromArgb(0, 59, 162); //blue pen daylight   weak
        //private static readonly Color COLOR_1 = Color.FromArgb(0, 39, 68); //blue jeans daylight   bad
        //private static readonly Color COLOR_1 = Color.FromArgb(0, 117, 243); // blue foam daylight   good
        //private static readonly Color COLOR_1 = Color.FromArgb(202, 172, 81); // yellow flour paper daylight    weak
        //private static readonly Color COLOR_1 = Color.FromArgb(0, 158, 161); // green webcam box daylight    good
        //private static readonly Color COLOR_1 = Color.FromArgb(0, 108, 213); // blue document holder daylight   good
        //private static readonly Color COLOR_1 = Color.FromArgb(192, 46, 103); // pink doll daylight      best
        //private static readonly Color COLOR_1 = Color.FromArgb(19, 54, 134); // purple sleeping mat daylight     good 
        private static readonly Color COLOR_2 = Color.FromArgb(61, 200, 111);
        private static readonly Color COLOR_3 = Color.FromArgb(57, 74, 200);
        private static readonly byte SEARCH_TOLERANCE = 20;
        private static readonly byte EXTEND_TOLERANCE = 35;
        private static readonly int SLEEP_TIME = 80;
        private static readonly int PAUSING_SLEEP_TIME = 200;
        private static readonly int MEASURING_INTERVAL = 4000;
        private static readonly short[] NEIGHBOURS_8_X = { -1, 0, 1, -1, 1, -1, 0, 1 };
        private static readonly short[] NEIGHBOURS_8_Y = { -1, -1, 1, 0, 0, 1, 1, 1 };

        private static readonly MarkerInImage MARKER_1 = new MarkerInImage("M01", "OrangeMarkerForLogiCam", COLOR_1, SEARCH_TOLERANCE, EXTEND_TOLERANCE, Color.Orange);
        private static readonly MarkerInImage MARKER_2 = new MarkerInImage("M02", "OrangeMarkerForLapCam", COLOR_1, SEARCH_TOLERANCE, EXTEND_TOLERANCE, Color.Orange);

        public MultiColorImageProcessor(Model model, VideoSource videoSource, string myId, string myName)
        {
            base.model = model;
            this.videoSource = (VideoSource)videoSource;
            base.processorSource = (ProcessorSource)this.videoSource;
            base.myId = myId;            
            base.myName = myName;
            videoSource.AddResultChangedHandler(this.SetSourceToChanged);
            
            base.result = new Dictionary<string, MarkerInImage>();            
            this.firstPixelsFound = new Dictionary<string, PointInt2D>();
            this.spats = new Dictionary<string, Spat>();
            base.resultChanged = new Dictionary<string, bool>();
            
                                                          
            if (videoSource.MyId.Contains("vid_05c8"))
            {
                ((Dictionary<string, MarkerInImage>)base.result).Add(MARKER_2.MyId, MARKER_2);
            }
            else
            {
                ((Dictionary<string, MarkerInImage>)base.result).Add(MARKER_1.MyId, MARKER_1);
            }

            this.sourceChangedLock = new Object();
            this.sourceChanged = false;                                    
            
            foreach (MarkerInImage localMarker in ((Dictionary<string, MarkerInImage>)base.result).Values)
            {
                ((Dictionary<string, bool>)this.resultChanged).Add(localMarker.MyId, false);
                this.firstPixelsFound.Add(localMarker.MyId, null);
                this.spats.Add(localMarker.MyId, null);
            }
            this.atLeastOneMarkerChanged = false;
                                
            base.averageProcessTime = 0.0;
            base.longestProcessTime = 0;
            this.myRunning = false;
        }

        public override void MyRun()
        {
            Stopwatch localStopwatch = new Stopwatch();
            localStopwatch.Start();
            Stopwatch searchStopwatch = new Stopwatch();
            Stopwatch extendStopwatch = new Stopwatch();
            long sumSearchTime = 0;
            long sumExtendTime = 0;            
            long longestSearchTime = 0;
            long longestExtendTime = 0;
            int localMeasuringLoops = 0;

            /* Forever while. */
            while (true)
            {
                /* Pausable while. */
                while (this.myRunning)
                {
                    /**
                     * For measuring the average search time of the search and extend algorithm.
                     */
                    if (localStopwatch.ElapsedMilliseconds > MEASURING_INTERVAL)
                    {
                        localStopwatch.Stop();

                        Console.WriteLine("SearchStopwatch measured an avg. run of {0}ms in {1}s on {2} iterations.", ((double)sumSearchTime / localMeasuringLoops).ToString("###0.00"), localStopwatch.ElapsedMilliseconds / 1000, localMeasuringLoops);
                        Console.WriteLine("ExtendStopwatch measured an avg. run of {0}ms in {1}s on {2} iterations.", ((double)sumExtendTime / localMeasuringLoops).ToString("###0.00"), localStopwatch.ElapsedMilliseconds / 1000, localMeasuringLoops);
                        this.averageProcessTime = (double)(sumSearchTime + sumExtendTime) / localMeasuringLoops;
                        this.longestProcessTime = (int)longestSearchTime + (int)longestExtendTime;
                        sumSearchTime = 0;
                        sumExtendTime = 0;
                        longestSearchTime = 0;
                        longestExtendTime = 0;
                        localMeasuringLoops = 0;

                        localStopwatch.Restart();
                    }

                    /**
                     * We are going to process an image that is a new one, freshly coming from the camera or video device.
                     * If ImageProcessor is so fast that it could process one image twice, why do it? Sleep instead. Rest, rest!
                     */
                    if (this.sourceChanged)
                    {
                        if (base.result == null || ((Dictionary<string, MarkerInImage>)base.result).Count <= 0
                            || base.processorSource == null || this.processorSource.ResultData == null)
                        {
                            // wow, that's rude
                            continue;
                        }

                        /**
                         * We can set this back now. It was used just for entering.
                         */
                        lock (this.sourceChangedLock)
                        {
                            this.sourceChanged = false;
                        }

                        /**
                         * Copy out the captured image to another instance.
                         * To run the search and other algorithms takes time. What if in that time the camera captures another
                         * image.
                         */
                        lock (((VideoSource)this.processorSource).imageLock)
                        {
                            this.copiedImageSize = new PointInt2D(this.videoSource.BitmapWidth, this.videoSource.BitmapHeight);
                            byte[] localCopiedSource = new byte[3 * this.copiedImageSize.X * this.copiedImageSize.Y];
                            ((byte[])base.processorSource.ResultData).CopyTo(localCopiedSource, 0);
                            base.copiedSourceData = localCopiedSource;
                        }

                        /**
                         * Search for Markers.
                         */
                        searchStopwatch.Start();
                        this.ToothedSpiralSearch();
                        searchStopwatch.Stop();
                        sumSearchTime += searchStopwatch.ElapsedMilliseconds;
                        if (searchStopwatch.ElapsedMilliseconds > longestSearchTime)
                        {
                            longestSearchTime = searchStopwatch.ElapsedMilliseconds;
                        }
                        searchStopwatch.Reset();

                        /**
                         * Extend those firstly found pixels in larger forms: Spats.
                         */
                        extendStopwatch.Start();
                        this.BreadthExtend();
                        extendStopwatch.Stop();
                        sumExtendTime += extendStopwatch.ElapsedMilliseconds;
                        if (extendStopwatch.ElapsedMilliseconds > longestExtendTime)
                        {
                            longestExtendTime = extendStopwatch.ElapsedMilliseconds;
                        }
                        extendStopwatch.Reset();

                        /** Deciding whether the markers have changed since last time (last loop iteration),
                         * and if yes, which of them have changed. */
                        Dictionary<string, MarkerInImage> markers = (Dictionary<string, MarkerInImage>)base.result;
                        this.atLeastOneMarkerChanged = false;
                        foreach (MarkerInImage localMarker in markers.Values)
                        {
                            string localId = localMarker.MyId;
                            Dictionary<string, bool> markersChanged = (Dictionary<string, bool>)base.resultChanged;
                            markersChanged[localId] = false;
                            if (this.firstPixelsFound[localId] != null)
                            {
                                markers[localId].Exists = true;
                                PointInt2D centerPixel = this.spats[localId].GetCenter();
                                PointReal2D newPosition = new PointReal2D((centerPixel.X - (float)copiedImageSize.X / 2) / ((float)copiedImageSize.X / 2), (centerPixel.Y - (float)copiedImageSize.Y / 2) / ((float)copiedImageSize.Y / 2));
                                if (!markers[localId].Position.Equals(newPosition))
                                {
                                    this.atLeastOneMarkerChanged = true;
                                    markersChanged[localId] = true;
                                    markers[localId].Position = newPosition;
                                }
                            }
                            else
                            {
                                if (markers[localId].Exists)
                                {
                                    markers[localId].Exists = false;
                                    this.atLeastOneMarkerChanged = true;
                                    markersChanged[localId] = true;
                                }
                            }

                        }

                        /** Invoking events for the change in the results from the Processor point of view
                         * AAND for the change in the sources from the ProcessorSource point of view.*/
                        if (this.atLeastOneMarkerChanged)
                        {
                            if (this.resultChangedCall != null)
                            {
                                this.resultChangedCall(this, this.resultChanged);
                            }
                        }

                        /**
                         * Invoking an event.
                         */
                        if (this.processedCall != null)
                        {
                            this.processedCall(this);
                        }

                        localMeasuringLoops++;
                        lock (base.nProcessingCyclesLock)
                        {
                            this.nProcessingCycles++;
                        }
                    }

                    try
                    {
                        Thread.Sleep(SLEEP_TIME);
                    }
                    catch (ThreadInterruptedException ex)
                    {
                        Console.WriteLine("whoops");
                        break;
                    }
                }

                try
                {
                    Thread.Sleep(PAUSING_SLEEP_TIME);
                }
                catch (ThreadInterruptedException ex)
                {
                    Console.WriteLine("whoops");
                    break;
                }
            }
        }

        public override void SetSourceToChanged(Object sender, Object whatChanged)
        {
            if (sender.Equals(this.videoSource))
            {
                lock (this.sourceChangedLock)
                {
                    this.sourceChanged = true;
                }
            }
        }

        public void MyStart()
        {
            this.myRunning = true;
        }

        public void MyStop()
        {
            this.myRunning = false;
        }

        public bool IsRunning()
        {
            return this.myRunning;
        }

        public override void AddResultChangedHandler(Graphene.Processing.ResultChangedHandler handler)
        {
            this.resultChangedCall += handler;
        }

        public override void RemoveResultChangedHandler(Graphene.Processing.ResultChangedHandler handler)
        {
            this.resultChangedCall -= handler;
        }

        public override void AddProcessedHandler(Graphene.Processing.ProcessedHandler handler)
        {
            this.processedCall += handler;
        }

        public override void RemoveProcessedHandler(Graphene.Processing.ProcessedHandler handler)
        {
            this.processedCall -= handler;
        }

        /** Searches in a spiral starting from the last known position of a marker.
         * When searching for a specific color it also watches after the others.
         * Keeps in memory the visited pixels. Compares every pixel max one time.
         * Parses in big steps, not from one to one, but in rarer lines.
         *
         * @param TOOTH_SIZE the number of how many pixels to omit when
         *      going in a direction. In an optimal case, it should be one
         *      pixel shorter than the size of the avarage color spat's diameter.
         * @return matrix representing the visited pixels of the image */
        private void ToothedSpiralSearch()
        {
            byte[] copiedByteImage = (byte[])base.copiedSourceData;

            /* Preconditions */
            if (copiedByteImage == null || this.copiedImageSize == null || TOOTH_SIZE <= 0)
            {
                return;
            }

            /* Initial settings */
            int primarilySearchedColorIndex;
            bool allMarkersFoundOrNotPresent;
            int imageSizeX = this.copiedImageSize.X;
            int imageSizeY = this.copiedImageSize.Y;
            int restMax;
            if (TOOTH_SIZE % 2 == 0)
            {
                restMax = TOOTH_SIZE + 1;
            }
            else
            {
                restMax = TOOTH_SIZE;
            }
            int[] rests = new int[restMax];
            rests[0] = 0;
            for (int i = 1; i <= restMax / 2; i++)
            {
                rests[i * 2 - 1] = +i;
                rests[i * 2] = -i;
            }
            int nMarkers = ((Dictionary<string, MarkerInImage>)this.result).Count;
            byte[] searchedRed = new byte[nMarkers];
            byte[] searchedGreen = new byte[nMarkers];
            byte[] searchedBlue = new byte[nMarkers];
            byte[] tolerance = new byte[nMarkers];            
            Dictionary<int, MarkerInImage> indexedMarkers = new Dictionary<int, MarkerInImage>();
            int j = 0;
            foreach (MarkerInImage localMarker in ((Dictionary<string, MarkerInImage>)base.result).Values)
            {
                indexedMarkers.Add(j, localMarker);                
                j++;
            }
            for (int i = 0; i < nMarkers; i++)
            {
                searchedRed[i] = indexedMarkers[i].Color.R;
                searchedGreen[i] = indexedMarkers[i].Color.G;
                searchedBlue[i] = indexedMarkers[i].Color.B;
                tolerance[i] = (byte)indexedMarkers[i].SearchShadeTolerance;
            }
            bool[] markerFoundOrNotPresent = new bool[nMarkers];
            for (int i = 0; i < nMarkers; i++)
            {
                markerFoundOrNotPresent[i] = false;
            }

            /* Spiral search start.
             * restIndexes are division rests. ex.: If we go in 4 pixel steps, rests will increase from 0 to 3. */
            int restIndexX = 0, restIndexY = 0;
            do
            {

                /* Choosing a color that is not found already. */
                for (primarilySearchedColorIndex = 0; primarilySearchedColorIndex < nMarkers; primarilySearchedColorIndex++)
                {
                    if (!markerFoundOrNotPresent[primarilySearchedColorIndex])
                    {
                        break;
                    }
                }

                /* Choosing the center of the spiral. If we know the position
                 * of the mark on the previous frame, we start from there.
                 * Otherwise from the last position we exited the previous search (no change). */
                int spiralCenterX, spiralCenterY;
                if (indexedMarkers[primarilySearchedColorIndex].Exists)
                {
                    spiralCenterX = (int)(indexedMarkers[primarilySearchedColorIndex].Position.X * copiedImageSize.X / 2 + (float)copiedImageSize.X / 2);
                    spiralCenterY = (int)(indexedMarkers[primarilySearchedColorIndex].Position.Y * copiedImageSize.Y / 2 + (float)copiedImageSize.Y / 2);
                    restIndexX = 0;
                    restIndexY = 0;
                }
                else
                {
                    spiralCenterX = (imageSizeX - 1) / 2;
                    spiralCenterY = (imageSizeY - 1) / 2;
                }

                /* Go through these modulo iterations. These modulo things
                 * are the parts that speed our algorithm up. */
                bool breakSearchCycle = false;
                restIndexY = 0;
                while (!breakSearchCycle && restIndexY < restMax)
                {
                    int restY = rests[restIndexY];
                    restIndexX = 0;
                    while (!breakSearchCycle && restIndexX < restMax)
                    {
                        int restX = rests[restIndexX];
                        int left = spiralCenterX + restX;

                        /* These ifs are for the example of:
                         * On the previous captured frame the marker was at (0, 0).
                         * When (restX, restY) = (-1, -1) the left and top will be -1 and -1, which is forbidden. */
                        if (left < 0)
                        {
                            left += TOOTH_SIZE;
                        }
                        if (left >= this.copiedImageSize.X)
                        {
                            left -= TOOTH_SIZE;
                        }
                        int right = spiralCenterX + restX;
                        if (right < 0)
                        {
                            right += TOOTH_SIZE;
                        }
                        if (right >= this.copiedImageSize.X)
                        {
                            right -= TOOTH_SIZE;
                        }
                        int top = spiralCenterY + restY;
                        if (top < 0)
                        {
                            top += TOOTH_SIZE;
                        }
                        if (top >= this.copiedImageSize.Y)
                        {
                            top -= TOOTH_SIZE;
                        }
                        int bottom = spiralCenterY + restY;
                        if (bottom < 0)
                        {
                            bottom += TOOTH_SIZE;
                        }
                        if (bottom >= this.copiedImageSize.Y)
                        {
                            bottom -= TOOTH_SIZE;
                        }
                        bool goTop = true;
                        bool goRight = true;
                        bool goBottom = true;
                        bool goLeft = true;

                        /* Go in a spiral until we reach all the sides of the image. */
                        while (!breakSearchCycle && (goTop || goBottom || goLeft || goBottom))
                        {
                            if (!breakSearchCycle && goTop)
                            {
                                int x = left;
                                while (!breakSearchCycle && x <= right)
                                {
                                    int byteAddress = 3 * (top * imageSizeX + x);
                                    byte foundRed = copiedByteImage[byteAddress + 2];
                                    byte foundGreen = copiedByteImage[byteAddress + 1];
                                    byte foundBlue = copiedByteImage[byteAddress];
                                    int secondarilySearchedColorIndex = 0;
                                    while (!breakSearchCycle && secondarilySearchedColorIndex < nMarkers)
                                    {
                                        if (!markerFoundOrNotPresent[secondarilySearchedColorIndex])
                                        {
                                            if (AbstractProcessor.CompareColor(searchedRed[secondarilySearchedColorIndex], searchedGreen[secondarilySearchedColorIndex], searchedBlue[secondarilySearchedColorIndex], foundRed, foundGreen, foundBlue, tolerance[secondarilySearchedColorIndex]))
                                            {
                                                markerFoundOrNotPresent[secondarilySearchedColorIndex] = true;
                                                this.firstPixelsFound[indexedMarkers[secondarilySearchedColorIndex].MyId] = new PointInt2D(x, top);
                                                if (secondarilySearchedColorIndex == primarilySearchedColorIndex)
                                                {
                                                    breakSearchCycle = true;
                                                }
                                            }
                                        }
                                        secondarilySearchedColorIndex++;
                                    }

                                    /* Instead of going smoothly from one neighbouring pixel to
                                     * the other, we go with jumps of <TOOTH_SIZE>. Usually we have
                                     * the luck of finding the colors faster. */
                                    x += TOOTH_SIZE;
                                }
                            }

                            if (!breakSearchCycle && goLeft)
                            {
                                int y = top;
                                while (!breakSearchCycle && y <= bottom)
                                {
                                    int byteAddress = 3 * (y * imageSizeX + left);
                                    byte foundRed = copiedByteImage[byteAddress + 2];
                                    byte foundGreen = copiedByteImage[byteAddress + 1];
                                    byte foundBlue = copiedByteImage[byteAddress];
                                    int secondarilySearchedColorIndex = 0;
                                    while (!breakSearchCycle && secondarilySearchedColorIndex < nMarkers)
                                    {
                                        if (!markerFoundOrNotPresent[secondarilySearchedColorIndex])
                                        {
                                            if (AbstractProcessor.CompareColor(searchedRed[secondarilySearchedColorIndex], searchedGreen[secondarilySearchedColorIndex], searchedBlue[secondarilySearchedColorIndex], foundRed, foundGreen, foundBlue, tolerance[secondarilySearchedColorIndex]))
                                            {
                                                markerFoundOrNotPresent[secondarilySearchedColorIndex] = true;
                                                this.firstPixelsFound[indexedMarkers[secondarilySearchedColorIndex].MyId] = new PointInt2D(left, y);
                                                if (secondarilySearchedColorIndex == primarilySearchedColorIndex)
                                                {
                                                    breakSearchCycle = true;
                                                }
                                            }
                                        }
                                        secondarilySearchedColorIndex++;
                                    }
                                    y += TOOTH_SIZE;
                                }
                            }

                            if (!breakSearchCycle && goBottom)
                            {
                                int x = left;
                                while (!breakSearchCycle && x <= right)
                                {
                                    int byteAddress = 3 * (bottom * imageSizeX + x);
                                    byte foundRed = copiedByteImage[byteAddress + 2];
                                    byte foundGreen = copiedByteImage[byteAddress + 1];
                                    byte foundBlue = copiedByteImage[byteAddress];
                                    int secondarilySearchedColorIndex = 0;
                                    while (!breakSearchCycle && secondarilySearchedColorIndex < nMarkers)
                                    {
                                        if (!markerFoundOrNotPresent[secondarilySearchedColorIndex])
                                        {
                                            if (AbstractProcessor.CompareColor(searchedRed[secondarilySearchedColorIndex], searchedGreen[secondarilySearchedColorIndex], searchedBlue[secondarilySearchedColorIndex], foundRed, foundGreen, foundBlue, tolerance[secondarilySearchedColorIndex]))
                                            {
                                                markerFoundOrNotPresent[secondarilySearchedColorIndex] = true;
                                                this.firstPixelsFound[indexedMarkers[secondarilySearchedColorIndex].MyId] = new PointInt2D(x, bottom);
                                                if (secondarilySearchedColorIndex == primarilySearchedColorIndex)
                                                {
                                                    breakSearchCycle = true;
                                                }
                                            }
                                        }
                                        secondarilySearchedColorIndex++;
                                    }
                                    x += TOOTH_SIZE;
                                }
                            }

                            if (!breakSearchCycle && goRight)
                            {
                                int y = top;
                                while (!breakSearchCycle && y <= bottom)
                                {
                                    int byteAddress = 3 * (y * imageSizeX + right);
                                    byte foundRed = copiedByteImage[byteAddress + 2];
                                    byte foundGreen = copiedByteImage[byteAddress + 1];
                                    byte foundBlue = copiedByteImage[byteAddress];
                                    int secondarilySearchedColorIndex = 0;
                                    while (!breakSearchCycle && secondarilySearchedColorIndex < nMarkers)
                                    {
                                        if (!markerFoundOrNotPresent[secondarilySearchedColorIndex])
                                        {
                                            if (AbstractProcessor.CompareColor(searchedRed[secondarilySearchedColorIndex], searchedGreen[secondarilySearchedColorIndex], searchedBlue[secondarilySearchedColorIndex], foundRed, foundGreen, foundBlue, tolerance[secondarilySearchedColorIndex]))
                                            {
                                                markerFoundOrNotPresent[secondarilySearchedColorIndex] = true;
                                                this.firstPixelsFound[indexedMarkers[secondarilySearchedColorIndex].MyId] = new PointInt2D(right, y);
                                                if (secondarilySearchedColorIndex == primarilySearchedColorIndex)
                                                {
                                                    breakSearchCycle = true;
                                                }
                                            }
                                        }
                                        secondarilySearchedColorIndex++;
                                    }
                                    y += TOOTH_SIZE;
                                }
                            }

                            /* We widen the spiral radius.
                             * If one one side it gets out the image,
                             * we put it back and keep in mind not to bother that side
                             * any more. */
                            if (goLeft)
                            {
                                if (left - TOOTH_SIZE >= 0)
                                {
                                    left -= TOOTH_SIZE;
                                }
                                else
                                {
                                    goLeft = false;
                                }
                            }
                            if (goRight)
                            {
                                if (right + TOOTH_SIZE < imageSizeX)
                                {
                                    right += TOOTH_SIZE;
                                }
                                else
                                {
                                    goRight = false;
                                }
                            }
                            if (goTop)
                            {
                                if (top - TOOTH_SIZE >= 0)
                                {
                                    top -= TOOTH_SIZE;
                                }
                                else
                                {
                                    goTop = false;
                                }
                            }
                            if (goBottom)
                            {
                                if (bottom + TOOTH_SIZE < imageSizeY)
                                {
                                    bottom += TOOTH_SIZE;
                                }
                                else
                                {
                                    goBottom = false;
                                }
                            }
                        }
                        restIndexX++;
                    }
                    restIndexY++;
                }

                if (!markerFoundOrNotPresent[primarilySearchedColorIndex])
                {
                    this.firstPixelsFound[indexedMarkers[primarilySearchedColorIndex].MyId] = null;
                    markerFoundOrNotPresent[primarilySearchedColorIndex] = true;
                }

                /* Updating the condition whether to continue the big while.
                 * If we've (found) or (have already searched and not found)
                 * a color, we are done. */
                allMarkersFoundOrNotPresent = true;
                for (int i = 0; i < nMarkers; i++)
                {
                    if (!markerFoundOrNotPresent[i])
                    {
                        allMarkersFoundOrNotPresent = false;
                        break;
                    }
                }

                /* If we parsed the image once totally, there is no need to
                 * search for colors any more. Cause we know that what's found
                 * is found, what's not it's not and neither will be. */
                if ((restIndexX >= restMax) && (restIndexY >= restMax))
                {
                    for (int i = 0; i < nMarkers; i++)
                    {
                        if (!markerFoundOrNotPresent[i])
                        {
                            markerFoundOrNotPresent[i] = true;
                            this.firstPixelsFound[indexedMarkers[i].MyId] = null;
                        }
                    }
                    allMarkersFoundOrNotPresent = true;
                }
            } while (!allMarkersFoundOrNotPresent);
        }

        /**
         * Searches around the firstPixelsFound in a Breadth-First-Search way to
         * add additional same colored pixels to the form, so we get a larger spat of mostly the same color
         * as the marker. By that we can compute its center of mass this way stabilizing the marker's position.
         */
        private void BreadthExtend()
        {
            byte[] copiedByteImage = (byte[])base.copiedSourceData;            

            /**
             * Initializations
             */
            int imageSizeX = this.copiedImageSize.X; int imageSizeY = this.copiedImageSize.Y;
            int nMarkers = ((Dictionary<string, MarkerInImage>)base.result).Values.Count;
            byte[] searchedR = new byte[nMarkers];
            byte[] searchedG = new byte[nMarkers];
            byte[] searchedB = new byte[nMarkers];
            byte[] tolerances = new byte[nMarkers];            
            Dictionary<int, MarkerInImage> indexedMarkers = new Dictionary<int, MarkerInImage>();
            int j = 0;
            foreach (MarkerInImage localMarker in ((Dictionary<string, MarkerInImage>)base.result).Values)
            {
                indexedMarkers.Add(j, localMarker);
                j++;
            }
            for (int i = 0; i < nMarkers; i++)
            {
                searchedR[i] = indexedMarkers[i].Color.R;
                searchedG[i] = indexedMarkers[i].Color.G;
                searchedB[i] = indexedMarkers[i].Color.B;
                tolerances[i] = indexedMarkers[i].SearchShadeTolerance;
            }

            for (int markerIndex = 0; markerIndex < nMarkers; markerIndex++)
            {
                PointInt2D fp = this.firstPixelsFound[indexedMarkers[markerIndex].MyId];
                if (fp == null)
                {
                    continue;
                }
                else
                {
                    Spat spat = new Spat();
                    Queue<PointInt2D> queue = new Queue<PointInt2D>();
                    queue.Enqueue(fp);
                    while (queue.Count > 0)
                    {
                        PointInt2D head = queue.Dequeue();
                        spat.MyAdd(head);
                        int px = head.X;
                        int py = head.Y;
                        for (int i = 0; i < 8; i++)
                        {
                            int neighbourX = px + NEIGHBOURS_8_X[i];
                            int neighbourY = py + NEIGHBOURS_8_Y[i];
                            PointInt2D neighbour = new PointInt2D(neighbourX, neighbourY);
                            if ((neighbourX >= 0) && (neighbourX < imageSizeX) && (neighbourY >= 0) && (neighbourY < imageSizeY)
                                    && (!spat.Contains(neighbour)) && (!queue.Contains(neighbour)))
                            {
                                int byteAddress = 3 * (neighbourY * imageSizeX + neighbourX);
                                byte foundRed = copiedByteImage[byteAddress + 2];
                                byte foundGreen = copiedByteImage[byteAddress + 1];
                                byte foundBlue = copiedByteImage[byteAddress];
                                if (AbstractProcessor.CompareColor(searchedR[markerIndex], searchedG[markerIndex], searchedB[markerIndex], foundRed, foundGreen, foundBlue, tolerances[markerIndex]))
                                {
                                    queue.Enqueue(neighbour);
                                }
                            }
                        }
                    }
                    this.spats[indexedMarkers[markerIndex].MyId] = spat;
                }
            }
        }


        /* Setters/getters: */

        public Dictionary<string, PointInt2D> FirstPixelsFound
        {
            get { return firstPixelsFound; }
        }

        public Dictionary<string, Spat> Spats
        {
            get { return spats; }
        }

        public PointInt2D CopiedImageSize
        {
            get { return copiedImageSize; }
        }


        /* Attributes: */

        /** Same as processorSource. Exactly the same. Only not polymorphed.
         * Not necessary. In fact, it's a bad plus. But i'll leave it here for a while. */
                     private VideoSource videoSource;

        /** Position of the very first pixel found on the last image similar to the markers' colors. */
        private Dictionary<string, PointInt2D> firstPixelsFound;

        /** Forms with all pixels having similar colors as their corresponding markers. */
        private Dictionary<string, Spat> spats;

        private PointInt2D copiedImageSize;

        /**
         * A lock to keep the sourceChanged attribute safe from concurrent threads.
         */
        private Object sourceChangedLock;

        /**
         * This is set to true whenever the incoming data, the source, has changed.
         */
        private bool sourceChanged;

        /* not even needed to be declared global class attribute*/
        private bool atLeastOneMarkerChanged;

        private bool myRunning;

        /** Methods (delegates) added to this attribute will be called whenever the results change.
         * This is source for the next processor after this one. */        
        private event Graphene.Processing.ResultChangedHandler resultChangedCall;

        private event Graphene.Processing.ProcessedHandler processedCall;
    }
}
